# _*_ coding utf-8 _*_
# FILENAME：wizard.py
# DESCRIPTION: 计算向导
# AUTH: 
# DATE: 2022/6/51:14 上午
import datetime
import logging
import sys, os
import threading
import time
import traceback

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, QCoreApplication
from PyQt5.QtGui import QPixmap, QColor, QFont
from PyQt5.QtWidgets import *

import algorithm
from algorithm.orderPlan import SchedulerUtils
from components.MessageBoxComponent import MessageBox
from components.SelectAlgorithmComponent import SelectAlgorithmComponent
from components.SelectFactoryTransportTimeComponent import SelectFactoryTransportTimeComponent
from components.SelectOperationTimeComponent import SelectOperationTimeComponent
from components.SelectShipperComponent import SelectShipperComponent
from components.SelectSupplierAddressComponent import SelectSupplierAddressComponent
from components.SelectSupplierDistanceComponent import SelectSupplierDistanceComponent
from components.SelectSupplierDurationComponent import SelectSupplierDurationComponent
from constant import CalculateType, CacheType
from models.asyncExecLogModel import AsyncCalculateLogModel
from models.baseModel import get_session
from models.orderPlanModel import OrderPlanModel, OrderPlanInventoryModel, OrderPlanPartsModel, OrderPlanDetailsModel
from utils.cacheUtils import CacheThread
from utils.confUtils import read_performance_config
from views.orderplan.inventoryDataGrid import InventoryDataGrid
from views.orderplan.requireSummaryDataGrid import DataGrid


class WizardPage1(QWizardPage):
    def __init__(self, parent=None):
        super(WizardPage1, self).__init__(parent)

        # 中间窗口部分显示的内容
        self.dataGrid = DataGrid()
        gLayout = QHBoxLayout()
        gLayout.addWidget(self.dataGrid)
        self.setLayout(gLayout)

        self.setTitle("<font color='white' style='font-size: 20px;'>零件数据设置</font>")
        self.setSubTitle('选择零件数据')


class WizardPage2(QWizardPage):
    def __init__(self, parent=None):
        super(WizardPage2, self).__init__(parent)

        # 中间窗口部分显示的内容
        self.dataGrid = InventoryDataGrid()
        gLayout = QHBoxLayout()
        gLayout.addWidget(self.dataGrid)
        self.setLayout(gLayout)

        self.setTitle("<font color='white' style='font-size: 20px;'>库存数据设置</font>")
        self.setSubTitle('选择库存数据')


class WizardPage3(QWizardPage):
    def __init__(self, parent=None):
        super(WizardPage3, self).__init__(parent)

        # 中间窗口部分显示的内容
        vLayout = QVBoxLayout(self)
        self.lab_supplier_address = QLabel('供应商地址:')
        self.lab_supplier_distance = QLabel('供应商距离:')
        self.lab_supplier_duration = QLabel('供应商路程时间:')
        self.lab_shipper = QLabel('承运商:')
        self.lab_operation_time = QLabel('作业时间:')
        self.lab_factory_transport_time = QLabel('厂内运转时间:')
        self.lab_algorithm = QLabel('算法:')

        self.combobox_supplier_address = SelectSupplierAddressComponent()
        self.combobox_supplier_address.setCurrentIndex(0)
        self.combobox_supplier_address.setFixedWidth(400)

        self.combobox_supplier_distance = SelectSupplierDistanceComponent()
        self.combobox_supplier_distance.setCurrentIndex(0)
        self.combobox_supplier_distance.setFixedWidth(400)

        self.combobox_supplier_duration = SelectSupplierDurationComponent()
        self.combobox_supplier_duration.setCurrentIndex(0)
        self.combobox_supplier_duration.setFixedWidth(400)

        self.combobox_shipper = SelectShipperComponent()
        self.combobox_shipper.setCurrentIndex(0)
        self.combobox_shipper.setFixedWidth(400)

        self.combobox_operation_time = SelectOperationTimeComponent()
        self.combobox_operation_time.setCurrentIndex(0)
        self.combobox_operation_time.setFixedWidth(400)

        self.combobox_factoryTransportTime = SelectFactoryTransportTimeComponent()
        self.combobox_factoryTransportTime.setCurrentIndex(0)
        self.combobox_factoryTransportTime.setFixedWidth(400)

        self.combobox_algorithm = SelectAlgorithmComponent()
        self.combobox_algorithm.setCurrentIndex(0)
        self.combobox_algorithm.setFixedWidth(400)

        widget = QWidget()
        widget.setFixedWidth(600)
        gLayout = QGridLayout()

        gLayout.addWidget(self.lab_supplier_address, 0, 0, 1, 1)
        gLayout.addWidget(self.combobox_supplier_address, 0, 1, 1, 1)

        gLayout.addWidget(self.lab_supplier_distance, 1, 0, 1, 1)
        gLayout.addWidget(self.combobox_supplier_distance, 1, 1, 1, 1)

        gLayout.addWidget(self.lab_supplier_duration, 2, 0, 1, 1)
        gLayout.addWidget(self.combobox_supplier_duration, 2, 1, 1, 1)

        gLayout.addWidget(self.lab_shipper, 3, 0, 1, 1)
        gLayout.addWidget(self.combobox_shipper, 3, 1, 1, 1)

        gLayout.addWidget(self.lab_operation_time, 4, 0, 1, 1)
        gLayout.addWidget(self.combobox_operation_time, 4, 1, 1, 1)

        gLayout.addWidget(self.lab_factory_transport_time, 5, 0, 1, 1)
        gLayout.addWidget(self.combobox_factoryTransportTime, 5, 1, 1, 1)

        gLayout.addWidget(self.lab_algorithm, 6, 0, 1, 1)
        gLayout.addWidget(self.combobox_algorithm, 6, 1, 1, 1)

        widget.setLayout(gLayout)
        vLayout.addWidget(widget)
        self.setLayout(vLayout)

        self.setButtonText(QWizard.NextButton, '计算')
        self.setTitle("<font color='white' style='font-size: 20px;'>其它参数设置</font>")
        self.setSubTitle('选择其它运算数据')


class WizardPage4(QWizardPage):
    def __init__(self, parent=None):
        super(WizardPage4, self).__init__(parent)

        # 中间窗口部分显示的内容
        vLayout = QVBoxLayout(self)
        info = QLabel(self)
        info.setFont(QFont(info.font().family(), 16))
        info.setAlignment(Qt.AlignCenter)
        info.setText('提交成功，正在后台进行运算，稍后请在运算日志中查看结果')
        vLayout.addStretch()
        vLayout.addWidget(info)
        vLayout.addStretch()

        self.setLayout(vLayout)

        self.setTitle("<font color='white' style='font-size: 20px;'>提交完成</font>")
        self.setSubTitle('提交计算')


class WizardTest(QWizard):
    __id = 0
    def __init__(self, parent=None, id=0):
        super(WizardTest, self).__init__(parent)
        self.__id = id
        self.setFixedWidth(900)
        self.setPage(1, WizardPage1(self))
        self.setPage(2, WizardPage2(self))
        self.setPage(3, WizardPage3(self))
        self.setPage(4, WizardPage4(self))

        # 去掉帮助按钮
        self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)

        # 设置导航样式
        self.setWizardStyle(QWizard.ModernStyle)
        # 设置导航窗口标题
        self.setWindowTitle('排单运算向导')

        # 去掉页面的一些按钮
        self.setOption(QWizard.NoBackButtonOnStartPage)  # 首页没有回退按钮
        self.setOption(QWizard.NoBackButtonOnLastPage)  # 最后一页没有回退按钮
        self.setOption(QWizard.NoCancelButton)  # 没有取消按钮

        # 设置导航栏背景标题
        pix = QPixmap(900, 64)
        pix.fill(QColor(52, 104, 192))
        self.setPixmap(QWizard.BannerPixmap, pix)

        # 设置标题栏图标
        # pix = QPixmap(os.path.dirname(__file__) + "/python.png")
        # self.setPixmap(QWizard.LogoPixmap, pix.scaled(48, 48))

        # 设置页面主标题显示格式
        self.setTitleFormat(Qt.RichText)
        # 设置子标题显示格式
        self.setSubTitleFormat(Qt.RichText)

        # 设置按钮的显示名称
        self.setButtonText(QWizard.NextButton, '下一步')
        self.setButtonText(QWizard.BackButton, '上一步')
        self.setButtonText(QWizard.FinishButton, '完成')

    def validateCurrentPage(self): # real signature unknown; restored from __doc__
        """ validateCurrentPage(self) -> bool """
        currentId = self.currentId()
        page = self.currentPage()
        if currentId == 1:
            #  汇总零件选择
            indexs = page.dataGrid.targetSelectModel.selectedRows()
            if len(indexs) == 0:
                messageBox = MessageBox()
                messageBox.warrning(title="提醒", msg="请选择参与计算的零件数据")
                return False
            else:
                self.partsIds = []
                self.partsVersionNames = []
                self.factoryNames = []
                for index in indexs:
                    row = index.row()
                    model = page.dataGrid.dataModel.item(row, 1)
                    self.partsIds.append(int(model.text()))
                    # 版本名称
                    version_model = page.dataGrid.dataModel.item(row, 2)
                    self.partsVersionNames.append(version_model.text())
                    # 工厂名称
                    factory_model = page.dataGrid.dataModel.item(row, 3)
                    self.factoryNames.append(factory_model.text())
                # 工厂去重
                self.factoryNames = list(set(self.factoryNames))
        elif currentId == 2:
            #  库存选择
            indexs = page.dataGrid.targetSelectModel.selectedRows()
            if len(indexs) == 0:
                messageBox = MessageBox()
                messageBox.warrning(title="提醒", msg="请选择参与计算的库存数据")
                return False
            else:
                self.InventoryIds = []
                self.InventoryVersionNames = []
                for index in indexs:
                    row = index.row()
                    model = page.dataGrid.dataModel.item(row, 1)
                    self.InventoryIds.append(int(model.text()))
                    # 版本名称
                    version_model = page.dataGrid.dataModel.item(row, 2)
                    self.InventoryVersionNames.append(version_model.text())
        elif currentId == 3:
            #  保存
            # print('保存')
            session = get_session()
            time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            # 版本描述
            version_desc = ','.join(self.factoryNames)
            # 更新主表
            params = {
                'operation_time_version_id': page.combobox_operation_time.getId(),
                'factory_transport_time_version_id': page.combobox_factoryTransportTime.getId(),
                'shipper_version_id': page.combobox_shipper.getId(),
                'supplier_address_version_id': page.combobox_supplier_address.getId(),
                'supplier_distance_version_id': page.combobox_supplier_distance.getId(),
                'supplier_duration_version_id': page.combobox_supplier_duration.getId(),
                'algorithm_id': page.combobox_algorithm.getId(),
                'version_desc': version_desc,
                'update_date': time
            }
            # 更新
            session.query(OrderPlanModel).filter(OrderPlanModel.id == self.__id).update(params)

            # 插入排单计划-库存版本
            session.query(OrderPlanInventoryModel).filter(OrderPlanInventoryModel.order_plan_id == self.__id).delete()
            order_plan_inventory_list = []
            for inventory_id in self.InventoryIds:
                order_plan_inventory = OrderPlanInventoryModel()
                order_plan_inventory.order_plan_id = self.__id
                order_plan_inventory.inventory_version_id = inventory_id

                order_plan_inventory_list.append(order_plan_inventory)

            session.bulk_save_objects(order_plan_inventory_list)

            # 插入排单计划-零件版本
            session.query(OrderPlanPartsModel).filter(OrderPlanPartsModel.order_plan_id == self.__id).delete()
            order_plan_parts_list = []
            for parts_id in self.partsIds:
                order_plan_parts = OrderPlanPartsModel()
                order_plan_parts.order_plan_id = self.__id
                order_plan_parts.parts_version_id = parts_id

                order_plan_parts_list.append(order_plan_parts)

            session.bulk_save_objects(order_plan_parts_list)

            # 提交&关闭
            session.commit()
            session.close()

            # 异步计算
            cal_thread = CalThread(self.__id)
            cal_thread.start()
        else:
            print(currentId)
        return True

class DemoWizard(QWidget):
    def __init__(self, parent=None):
        super(DemoWizard, self).__init__(parent)

        # 设置窗口标题
        self.setWindowTitle('实战PyQt5: QWizard Demo!')
        # 设置窗口大小
        self.resize(800, 120)

        self.initUi()

    def initUi(self):
        vLayout = QVBoxLayout(self)
        btnTest = QPushButton('向导对话框测试', self)
        btnTest.clicked.connect(self.onButtonTest)
        vLayout.addWidget(btnTest)
        self.setLayout(vLayout)

    def onButtonTest(self):
        dlg = WizardTest(self)
        dlg.exec()



"""
计算排单计划
"""


class CalThread(threading.Thread):
    def __init__(self, order_plan_id):
        super(CalThread, self).__init__()  # 重构run函数必须写
        self.order_plan_id = order_plan_id

    def run(self):
        session = get_session()
        # 更新运算状态
        session.query(OrderPlanModel).filter(OrderPlanModel.id == self.order_plan_id).update({'status': 2})
        # 写日志表
        log = AsyncCalculateLogModel()
        log.type = CalculateType.ORDER_PLAN.value
        log.version_id = self.order_plan_id
        log.status = 0
        log.start_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        session.add(log)
        # 提交&关闭
        session.commit()
        log_id = log.id
        session.close()

        try:
            # 运算
            order_plan_details = []
            order_plan_algorithm = SchedulerUtils()
            cal_order_plan_details, cal_statistics, init_time, cal_time = order_plan_algorithm.get_schedule(self.order_plan_id)

            session = get_session()
            # 分批保存数量
            bulk_save_count = read_performance_config('bulk_save_count')
            # 删除上次运算
            session.query(OrderPlanDetailsModel).filter(OrderPlanDetailsModel.order_plan_id == self.order_plan_id).delete()
            # 保存数据
            save_start_time = int(time.time())

            for data in cal_order_plan_details:
                order_plan_detail = OrderPlanDetailsModel()
                order_plan_detail.order_plan_id = self.order_plan_id
                order_plan_detail.van_code = data['货车编号（牌照号）']
                order_plan_detail.van_size = str(data['物流车型'])
                order_plan_detail.supplier_name = data['供应商名称'] if data['供应商名称'] else None
                order_plan_detail.supplier_address = data['供应商地址'] if data['供应商地址'] else None
                order_plan_detail.parts_code = data['零件编号'] if data['零件编号'] else None
                order_plan_detail.parts_name = data['零件名称'] if data['零件名称'] else None
                order_plan_detail.parts_quantity = data['零件数量'] if data['零件数量'] else None
                order_plan_detail.parts_box_quantity = data['箱数'] if data['箱数'] else None
                order_plan_detail.parts_snp = data['单包装容量'] if data['单包装容量'] else None
                order_plan_detail.parts_packaging_type = data['包装类型'] if data['包装类型'] else None
                order_plan_detail.factory = data['目的地工厂']
                order_plan_detail.unloading_crossing = data['交付道口'] if data['交付道口'] else None
                order_plan_detail.distance = data['路程距离'] if data['路程距离'] else None
                order_plan_detail.loading_percentage = data['装载率'] if data['装载率'] else None
                order_plan_detail.cost = data['路程成本'] if data['路程成本'] else None
                order_plan_detail.trip_number = data['趟数']
                order_plan_detail.arrival_time = data['预计抵达时间']

                order_plan_details.append(order_plan_detail)

                # 分批保存
                if 0 < int(bulk_save_count) <= len(order_plan_details):
                    # 保存
                    session.bulk_save_objects(order_plan_details)
                    order_plan_details = []

            # 保存
            session.bulk_save_objects(order_plan_details)
            # 保存汇总
            if cal_statistics:
                order_plan_data = {}
                total_trip_number = 0
                total_cost = 0
                total_loading_percentage = 0
                count = 0

                order_plan_data['van_enough'] = 1
                for data in cal_statistics:
                    total_trip_number += data['总趟数']
                    total_cost += data['总路程成本']
                    total_loading_percentage += data['平均装载率']
                    count += 1

                    if data['车辆充足'] == '不充足':
                        order_plan_data['van_enough'] = 0

                order_plan_data['trip_number'] = total_trip_number
                order_plan_data['cost'] = total_cost
                order_plan_data['loading_percentage'] = round(total_loading_percentage / count, 2)

                session.query(OrderPlanModel).filter(OrderPlanModel.id == self.order_plan_id).update(order_plan_data)
            session.commit()
        except Exception as e:
            # 回滚
            session.rollback()
            # 更新运算状态
            session.query(OrderPlanModel).filter(OrderPlanModel.id == self.order_plan_id).update({'status': 4})
            # 更新日志
            session.query(AsyncCalculateLogModel).filter(AsyncCalculateLogModel.id == log_id).update({
                'status': 2,
                'end_time': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                'reason': e
            })
            session.commit()

            # 记录日志
            logging.error(traceback.format_exc())
        else:
            # 更新运算状态
            session.query(OrderPlanModel).filter(OrderPlanModel.id == self.order_plan_id).update({'status': 3})
            # 更新日志
            session.query(AsyncCalculateLogModel).filter(AsyncCalculateLogModel.id == log_id).update({
                'status': 1,
                'end_time': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                'init_time': init_time,
                'cal_time': cal_time,
                'save_time': int(time.time()) - save_start_time
            })
            # 提交&关闭
            session.commit()

            # 缓存数据
            cache_thread = CacheThread(CacheType.ORDER_PLAN.value, self.order_plan_id)
            cache_thread.start()
        finally:
            session.close()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = DemoWizard()
    window.show()
    sys.exit(app.exec())
